/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.tools.idea.gradle.service.notification

import com.android.tools.idea.gradle.project.build.output.BuildOutputParserUtils.isCompilationFailureLine
import com.android.tools.idea.gradle.project.build.output.JavaLanguageLevelDeprecationOutputParser
import com.intellij.execution.rmi.RemoteUtil
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationExtension
import com.intellij.util.ExceptionUtil
import org.jetbrains.plugins.gradle.util.GradleConstants

/**
 * This class is added to implement [isInternalError] for failures that we want to attribute directly to the failed tasks.
 *
 * This class is added to suppress top-level failures generated from build exceptions that instead should be
 * attributed to failed tasks. This filter stops exception from being converted to the Failure object and shown as a build error
 * on the top level in Build Output.
 *
 * Examples of such failures generated by AGP:
 *  - "Java compiler version 7 has removed support for compiling with source/target version 21." error.
 */
class AGPMessagesNotificationExtension: ExternalSystemNotificationExtension {

  override fun getTargetExternalSystemId(): ProjectSystemId = GradleConstants.SYSTEM_ID

  override fun isInternalError(error: Throwable): Boolean {
    return isXmlProcessorError(error)
           || isJavaLanguageLevelDeprecationError(error)
           || isCompilationError(error)
  }

  private fun isXmlProcessorError(error: Throwable): Boolean {
    // See b/280524982
    val rootCause = ExceptionUtil.getRootCause(error)
    return rootCause is javax.xml.stream.XMLStreamException
           && rootCause.stackTrace.any { it.className == "com.android.aaptcompiler.XmlProcessor" }
  }

  private fun isJavaLanguageLevelDeprecationError(error: Throwable): Boolean {
    val firstLine = error.message?.lineSequence()?.firstOrNull()
    return firstLine != null && JavaLanguageLevelDeprecationOutputParser.javaVersionRemovedPattern.matcher(firstLine).matches()
  }

  private fun isCompilationError(error: Throwable): Boolean {
    // See b/278800524
    // org.jetbrains.plugins.gradle.service.notification.GradleNotificationExtension.isInternalError filters out
    // compilation errors from javac but not from kotlin. Failure parsers however (GradleBuildFailureParser)
    // filter out all compilation errors, they are to be handled by specific parsers.
    // Add same filtering as in GradleBuildFailureParser here so that empty error node is not created from exception.
    val unwrapped = RemoteUtil.unwrap(error)
    val messageFirstLine = unwrapped.message?.lineSequence()?.firstOrNull()
    return messageFirstLine?.isCompilationFailureLine() == true
  }
}